#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../inc/defs.h"
#include "../inc/priorityQueue.h"
#include "../inc/log.h"

/* Tipos de Datos a utilizar en la cola de prioridades */

/* Tipo Nodo */
typedef struct nodeT
{
	pqelementT elem;
	struct nodeT *nextNode;
} nodeT;

/* Tipo Queue */
typedef struct queueNodeT
{
	int priority;
	nodeT *nodeIni;
	nodeT *nodeFin;
	struct queueNodeT *next;
} queueNodeT;

/* Tipo de dato oculto */
struct pqueueCDT
{
	int cantQueue;
	queueNodeT *queueIni;
};

/* Funciones privadas */
static void FreePQueue_W(queueNodeT *nodoQ);
static void FreeElements(nodeT *nodo);

pqueueADT
NewPQueue(void)
{
	pqueueADT pq;

	if ( (pq = calloc(1, sizeof(struct pqueueCDT))) == NULL )
	{
		Fatal("NewPQueue@pqueueADT.c - Not enough memory.\n");
		return NULL;
	}

	return pq;
}

int
PEnqueue(pqueueADT pqueue, pqelementT element, int priority)
{
	queueNodeT *nodoq, *aux, *ant;
	nodeT *nodo;

	if ( Precondicion(pqueue) || Precondicion(element) || priority < 0 )
	{
		Error("PEnqueue@pqueueADT.c - Invalid arguments\n");
		return 0;
	}

	/* Creo un nodo para la cola de prioridades */
	if ( (nodo = calloc(1, sizeof(nodeT))) == NULL )
	{
		Fatal("PEnqueue@pqueueADT.c - Not enough memory\n");
		return 0;
	}

	ant = aux = pqueue->queueIni;
	while (aux != NULL && (aux->priority < priority))
	{
		ant = aux;
		aux = aux->next;
	}

	if (aux != NULL && (aux->priority == priority)) /* Agrego el elemento a la cola de prioridades existentes */
	{
		nodo->elem = element;
		aux->nodeFin->nextNode = nodo;
		aux->nodeFin =  nodo;
		pqueue -> cantQueue++;
		return 1;
	}

	/* Creo un nuevo nodo de cola de prioridades */
	if ( (nodoq = calloc(1, sizeof(queueNodeT))) == NULL )
	{
		Fatal("PEnqueue@pqueueADT.c - Not enough memory\n");
		free(nodo);
		return 0;
	}

	nodoq->next = aux;

	if (ant == aux)
		pqueue->queueIni = nodoq;
	else
		ant->next = nodoq;

	nodoq->nodeFin = nodoq->nodeIni = nodo;
	nodoq->priority = priority;
	nodo->elem = element;
	pqueue->cantQueue++;
	return 1;
}


int
PQueueIsEmpty(pqueueADT pqueue)
{
	return (pqueue->cantQueue == 0);
}

pqelementT
PDequeue(pqueueADT pqueue)
{
	pqelementT elem;
	queueNodeT *nodo, *nodoaux;
	nodeT * mnodo;

	if ( Precondicion(pqueue) )
	{
		Error("PDequeue@pqueueADT.c - Invalid argument\n");
		return NULL;
	}

	if (!PQueueIsEmpty(pqueue))
	{
		nodo = nodoaux = pqueue -> queueIni;
		mnodo = nodo -> nodeIni;
		elem = mnodo -> elem;


		mnodo->elem = NULL;
		/* Destruye en la cola de prioridades el nodo que almacenaba al elemento devuelto */

		/* Primero elemento de la primera cola pasa  a ser el siguiente del actual */
		nodo -> nodeIni = nodo -> nodeIni -> nextNode;
		mnodo -> nextNode = NULL;
		free(mnodo);

		/* si se vacio la primera cola de prioridad hay que liberar su header
		 * y apunta a la siguiente cola */
		if (nodo -> nodeIni == NULL)
		{
			pqueue -> queueIni = nodo -> next;
			free(nodo);
		}
	}
	else
		elem = NULL;

	pqueue -> cantQueue--;

	return elem;
}

void
FreePQueue(pqueueADT pqueue)
{
	if ( Precondicion(pqueue) )
	{
		Error("FreePQueue@pqueueADT.c - Invalid argument\n");
		return;
	}

	FreePQueue_W(pqueue->queueIni);
	free(pqueue);
}

static void
FreePQueue_W(queueNodeT *nodoQ)
{
	if (nodoQ == NULL)
		return;

	FreePQueue_W(nodoQ->next);
	FreeElements(nodoQ->nodeIni);

	nodoQ->nodeIni = nodoQ->nodeFin = NULL;
	free(nodoQ);
}

static void
FreeElements(nodeT *nodo)
{
	if (nodo == NULL)
		return;

	FreeElements(nodo->nextNode);

	nodo->elem = NULL;
	free(nodo);
}

